use ash::vk::{QueueFamilyProperties, QueueFlags};
use ash::{Entry, Instance};
use ash::{vk, vk::SurfaceKHR};
use ash::prelude::VkResult;

#[cfg(target_os = "windows")]
use ash::extensions::khr::Win32Surface;
#[cfg(all(unix, not(target_os = "android"), not(target_os = "macos")))]
use ash::extensions::khr::XlibSurface;
#[cfg(target_os = "macos")]
use ash::extensions::mvk::MacOSSurface;

#[cfg(target_os = "macos")]
use cocoa::appkit::{NSView, NSWindow};
#[cfg(target_os = "macos")]
use cocoa::base::id as cocoa_id;
#[cfg(target_os = "macos")]
use metal::CoreAnimationLayer;
#[cfg(target_os = "macos")]
use objc::runtime::YES;

use ash::extensions::khr::Surface as KhrSurfaceFnSet;
use winit::window::Window;

use crate::{VK_CHECK, VK_LOGP, VK_LOGV, VK_CHECK_UNSAFE};

use super::{VkSurface, VkSwapchainSupport};

impl VkSurface {
    pub fn new(entry: &Entry, instance: &Instance, w_window: &Window) -> Self {
        Self {
            handler         : unsafe { 
                match VK_CHECK!(Self::create_surface(entry, instance, w_window))  {
                    Some(inner_value) => inner_value,
                    None => VK_LOGP!("Failed to Create Surface")
                }
            },
            function_set    : KhrSurfaceFnSet::new(entry, instance)
        }
    }

    #[cfg(all(unix, not(target_os = "android"), not(target_os = "macos")))]
    pub unsafe fn create_surface<E: EntryV1_0, I: InstanceV1_0>(
        entry: &E,
        instance: &I,
        window: &winit::window::Window,
    ) -> Result<vk::SurfaceKHR, vk::Result> {
        use std::ptr;
        use winit::platform::unix::WindowExtUnix;

        let x11_display = window.xlib_display().unwrap();
        let x11_window = window.xlib_window().unwrap();
        let x11_create_info = vk::XlibSurfaceCreateInfoKHR {
            s_type: vk::StructureType::XLIB_SURFACE_CREATE_INFO_KHR,
            p_next: ptr::null(),
            flags: Default::default(),
            window: x11_window as vk::Window,
            dpy: x11_display as *mut vk::Display,
        };
        let xlib_surface_loader = XlibSurface::new(entry, instance);
        xlib_surface_loader.create_xlib_surface(&x11_create_info, None)
    }

    #[cfg(target_os = "macos")]
    pub unsafe fn create_surface(
        entry: &ash::Entry,
        instance: &ash::Instance,
        window: &winit::window::Window,
    ) -> Result<vk::SurfaceKHR, vk::Result> {
        use std::mem;
        use std::os::raw::c_void;
        use std::ptr;
        use winit::platform::macos::WindowExtMacOS;

        let wnd: cocoa_id = mem::transmute(window.ns_window());

        let layer = CoreAnimationLayer::new();

        layer.set_edge_antialiasing_mask(0);
        layer.set_presents_with_transaction(false);
        layer.remove_all_animations();

        let view = wnd.contentView();

        layer.set_contents_scale(view.backingScaleFactor());
        view.setLayer(mem::transmute(layer.as_ref()));
        view.setWantsLayer(YES);

        let create_info = vk::MacOSSurfaceCreateInfoMVK {
            s_type: vk::StructureType::MACOS_SURFACE_CREATE_INFO_MVK,
            p_next: ptr::null(),
            flags: Default::default(),
            p_view: window.ns_view() as *const c_void,
        };

        let macos_surface_loader = MacOSSurface::new(entry, instance);
        macos_surface_loader.create_mac_os_surface(&create_info, None)
    }

    #[cfg(target_os = "windows")]
    pub unsafe fn create_surface(
        entry: &ash::Entry,
        instance: &ash::Instance,
        window: &winit::window::Window,
    ) -> VkResult<vk::SurfaceKHR> {
        use std::os::raw::c_void;
        use std::ptr;
        use winapi::shared::windef::HWND;
        use winapi::um::libloaderapi::GetModuleHandleW;
        use winit::platform::windows::WindowExtWindows;

        let hwnd = window.hwnd() as HWND;
        let hinstance = GetModuleHandleW(ptr::null()) as *const c_void;

        let win32_create_info = vk::Win32SurfaceCreateInfoKHR {
            s_type          : ash::vk::StructureType::WIN32_SURFACE_CREATE_INFO_KHR,
            p_next          : ptr::null(),
            flags           : Default::default(),
            hinstance       : hinstance,
            hwnd            : hwnd as *const c_void,
        };
        
        let win32_surface_loader = Win32Surface::new(entry, instance);
        win32_surface_loader.create_win32_surface(&win32_create_info, None)
    }

    pub fn query_device_swapchain_support(&self, vk_physical_device: &vk::PhysicalDevice) -> VkSwapchainSupport {
        let surface_formats = match VK_CHECK_UNSAFE!(self.function_set.get_physical_device_surface_formats(*vk_physical_device, self.handler)) {
            Some(inner_value) => { inner_value },
            None => {VK_LOGP!("Failed To get_physical_device_surface_formats");}
        };
        let surface_present_modes = match VK_CHECK_UNSAFE!(self.function_set.get_physical_device_surface_present_modes(*vk_physical_device, self.handler)) {
            Some(inner_value) => { inner_value },
            None => {VK_LOGP!("Failed To get_physical_device_surface_present_modes");}
        };
        let surface_capabilities = match VK_CHECK_UNSAFE!(self.function_set.get_physical_device_surface_capabilities(*vk_physical_device, self.handler)) {
            Some(inner_value) => { inner_value },
            None => {VK_LOGP!("Failed To get_physical_device_surface_capabilities");}
        };

        VkSwapchainSupport {
            surface_formats         : surface_formats,
            surface_present_modes   : surface_present_modes,
            surface_capabilities    : surface_capabilities
        }
    }

    pub fn check_queue_support(&self, vk_physical_device: &vk::PhysicalDevice) {
        
        // match VK_CHECK!(unsafe { self.function_set.get_physical_device_surface_support(*vk_physical_device, queue_family_index as u32, self.handler) }) {
        //     Some(is_supported) => { if is_supported { 
        //         queue.push(VkQueue { 
        //             family_index    : queue_family_index as u32,
        //             queue_index     : queue_counter
        //         });
        //         queue_counter = queue_counter + 1;
        //     } },
        //     None => { VK_LOGP!("Failed to get_physical_device_surface_support") }
        // }
    }
}